﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Configuration;
using System.Xml;
using SocialFront.Providers;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.template;
using umbraco.cms.businesslogic.web;
using umbraco.DataLayer;

namespace SocialFront.Umbraco
{
    public class Install
    {
        /// <summary>
        /// Creates the community area for Social Front below the specified node id.
        /// </summary>
        /// <param name="parentNodeId">The parent node id.</param>
        /// <param name="pathToXmlFile">The path to XML file.</param>
        public static bool CreateCommunityArea(int parentNodeId, string pathToXmlFile)
        {
            string pathToCommunityStructure = pathToXmlFile + "umbraco/plugins/SocialFront/SFCommunityStructure.config";
            string pathToCommunityMedia = pathToXmlFile + "umbraco/plugins/SocialFront/SFMediaStructure.config";
            string pathToProviderConfig = pathToXmlFile + "umbraco/plugins/SocialFront/SFProviders.config";
            string pathToRelations = pathToXmlFile + "umbraco/plugins/SocialFront/SFRelations.config";
            string pathToMembers = pathToXmlFile + "umbraco/plugins/SocialFront/SFMemberTypes.config";
            bool isStructureCreated = false;
            bool isConfigSetup = false;
            bool areRelationsCreated = false;

            // Create system user
            User systemUser = CreateSystemUser();

            if (File.Exists(pathToCommunityMedia))
            {
                XmlDocument structure = new XmlDocument();
                structure.Load(pathToCommunityMedia);

                // Create the community node in the content tree for the member
                if (structure.DocumentElement != null)
                {
                    foreach (XmlElement node in structure.DocumentElement.SelectNodes("node"))
                    {
                        CreateMediaAndAppSetting(node, parentNodeId, systemUser);
                    }
                    isStructureCreated = true;
                }
            }

            if (File.Exists(pathToCommunityStructure))
            {
                XmlDocument structure = new XmlDocument();
                structure.Load(pathToCommunityStructure);

                // Create the community node in the content tree for the member
                if (structure.DocumentElement != null)
                {
                    foreach (XmlElement node in structure.DocumentElement.SelectNodes("node"))
                    {
                        CreateDocumentAndAppSetting(node, parentNodeId, systemUser);
                    }
                    isStructureCreated = true;
                }
            }

            // TODO: Implement error handling to return messages to the UI if the config files don't exist (and all other errors too)
            if (File.Exists(pathToProviderConfig))
            {
                isConfigSetup = AddProviderToWebConfig(pathToProviderConfig);
            }

            // Create relationship types in the database from the xml file
            if (File.Exists(pathToRelations))
            {
                areRelationsCreated = CreateRelationshipTypes(pathToRelations);
            }

            // setup Member Types and Member Groups
            CreateMemberSetup(pathToMembers);

            if (isStructureCreated && isConfigSetup && areRelationsCreated) return true;

            return false;
        }

        // CreateCommunityStructure

        /// <summary>
        /// Creates the document. Recursive: creates all child nodes as well.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="parentNodeId">The parent node id.</param>
        /// <param name="systemUser">The system user.</param>
        private static void CreateMediaAndAppSetting(XmlNode node, int parentNodeId, User systemUser)
        {
            // Only create a content node if this is not just an app setting
            if (node.Attributes["appSettingOnly"] == null || node.Attributes["appSettingOnly"].Value == "false")
            {
                Media newMedia = Media.MakeNew(node.Attributes["nodeName"].Value,
                                                   MediaType.GetByAlias("Folder"),
                                                   systemUser, parentNodeId);

                if (node.Attributes["appSettingName"] != null && node.Attributes["appSettingName"].Value != "")
                {
                    AddAppSettingToWebConfig(node.Attributes["appSettingName"].Value, newMedia.Id.ToString());
                }

                foreach (XmlElement childNode in node.ChildNodes)
                {
                    CreateMediaAndAppSetting(childNode, newMedia.Id, systemUser);
                }
            }
        }

        /// <summary>
        /// Creates the document. Recursive: creates all child nodes as well.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="parentNodeId">The parent node id.</param>
        /// <param name="systemUser">The system user.</param>
        private static void CreateDocumentAndAppSetting(XmlNode node, int parentNodeId, User systemUser)
        {
            // Only create a content node if this is not just an app setting
            if (node.Attributes["appSettingOnly"] == null || node.Attributes["appSettingOnly"].Value == "false")
            {
                Document newDoc = Document.MakeNew(node.Attributes["nodeName"].Value,
                                                   DocumentType.GetByAlias(node.Attributes["nodeTypeAlias"].Value),
                                                   systemUser, parentNodeId);

                if (node.Attributes["templateAlias"].Value != "")
                {
                    //newDoc.Template = Template.GetTemplateIdFromAlias(node.Attributes["templateAlias"].Value);
                    newDoc.Template = Template.GetByAlias(node.Attributes["templateAlias"].Value).Id;
                }

                // Set UmbracoNaviHide if appropriate
                if (node.Attributes["umbracoNaviHide"] != null && node.Attributes["umbracoNaviHide"].Value == "true")
                {
                    newDoc.getProperty("umbracoNaviHide").Value = true;
                }

                // Save, publish and update document cache
                newDoc.Save();
                newDoc.Publish(systemUser);

                // Record the id of the new document in the app settings, if an appSettingName was specified
                if (node.Attributes["appSettingName"] != null && node.Attributes["appSettingName"].Value != "")
                {
                    AddAppSettingToWebConfig(node.Attributes["appSettingName"].Value, newDoc.Id.ToString());
                }

                // Update the document cache for the new document, and repeat for any child documents
                umbraco.library.UpdateDocumentCache(newDoc.Id);

                foreach (XmlElement childNode in node.ChildNodes)
                {
                    CreateDocumentAndAppSetting(childNode, newDoc.Id, systemUser);
                }
            }
            else if (node.Attributes["appSettingName"] != null && node.Attributes["appSettingName"].Value != "")
            {
                AddAppSettingToWebConfig(node.Attributes["appSettingName"].Value, node.Attributes["appSettingValue"].Value);
            }

        }

        /// <summary>
        /// Adds the providers to web config.
        /// </summary>
        /// <param name="providerSetup">The provider setup.</param>
        private static bool AddProviderToWebConfig(string pathToProviderConfig)
        {
            XmlDocument structure = new XmlDocument();
            structure.Load(pathToProviderConfig);
            if (structure.DocumentElement != null)
            {
                Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
                foreach (XmlElement node in structure.DocumentElement.SelectNodes("provider"))
                {
                    if (config.HasFile)
                    {
                        AddProvider(config, node);
                    }
                }
                config.Save();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Adds the provider.
        /// </summary>
        /// <param name="config">The config.</param>
        /// <param name="node">The node.</param>
        private static void AddProvider(Configuration config, XmlNode node)
        {
            SocialFrontProviderConfiguration configuration = new SocialFrontProviderConfiguration();
            configuration.Name = node.Attributes["name"].Value;
            configuration.Default = node.Attributes["defaultProvider"].Value;
            configuration.Providers.Add(new ProviderSettings("name", "type"));
            foreach (XmlNode childNode in node.ChildNodes)
            {
                AddProviderSettingsCollection(configuration, childNode.Attributes["name"].Value, childNode.Attributes["type"].Value);
            }
            config.Sections.Add(configuration.Name, configuration);
        }

        /// <summary>
        /// Adds the provider settings collection.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <param name="providerSetting">The provider setting.</param>
        private static void AddProviderSettingsCollection(SocialFrontProviderConfiguration configuration, string name, string type)
        {
            ProviderSettingsCollection psc = new ProviderSettingsCollection();
            psc.Add(new ProviderSettings(name, type));
            configuration.Providers = psc;
        }

        /// <summary>
        /// Creates a new key-value pair in the Appsettings in the web config.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public static void AddAppSettingToWebConfig(string key, string value)
        {
            Configuration myConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
            myConfig.AppSettings.Settings.Add(new KeyValueConfigurationElement(key, value));
            myConfig.Save();
        }

        /// <summary>
        /// Creates the system user.
        /// </summary>
        /// <returns>The id of the system user just created.</returns>
        public static User CreateSystemUser()
        {
            User.MakeNew("SocialFrontSystemUser", "SocialFrontSystemUser", "password", "admin@socialfront.org", UserType.GetUserType(1));

            User systemUser = new User("SocialFrontSystemUser");

            // TODO: disable umbraco access for system user

            AddAppSettingToWebConfig("sfSystemUserId", systemUser.Id.ToString());
            return systemUser;
        }


        /// <summary>
        /// Creates the relationship types.
        /// </summary>
        /// <param name="pathToXmlFile">The path to XML file.</param>
        /// <returns></returns>
        public static bool CreateRelationshipTypes(string pathToRelations)
        {
            XmlDocument data = new XmlDocument();

            // Going to use the umbraco datalayer sql helper class to insert our SQL - inline! Oh yeah, old school.
            // Get the Umbraco connectionstring from the web.config
            var sqlHelper = DataLayerHelper.CreateSqlHelper(ConfigurationManager.AppSettings["umbracoDbDSN"]);
            string commandText = "";

            if (File.Exists(pathToRelations))
            {
                data.Load(pathToRelations);

                // Create the community node in the content tree for the member
                if (data.DocumentElement != null)
                {
                    // Loop over each relation in the xml doc and do a sql insert for each one
                    foreach (XmlElement node in data.DocumentElement.SelectNodes("umbracoRelationType"))
                    {
                        commandText =
                            String.Format(
                                "INSERT INTO umbracoRelationType ( dual, parentObjectType, childObjectType, name, alias) VALUES({0}, '{1}', '{2}', '{3}','{4}')",
                                node.Attributes["dual"].Value,
                                node.Attributes["parentObjectType"].Value,
                                node.Attributes["childObjectType"].Value,
                                node.Attributes["name"].Value,
                                node.Attributes["alias"].Value
                                );
                        try
                        {
                            // Use the umbraco data layer sql helper to execute the command
                            sqlHelper.ExecuteNonQuery(commandText);
                        }
                        catch (Exception e)
                        {
                            throw new SqlHelperException("ExecuteNonQuery", commandText, null, e);
                        }
                    }
                    return true;
                }
            }
            return false;
        }

        public static void CreateMemberSetup(string memberConfig)
        {
             XmlDocument data = new XmlDocument();

             if (File.Exists(memberConfig))
             {
                 data.Load(memberConfig);
                 // Create the community node in the content tree for the member
                 if (data.DocumentElement != null)
                 {
                     // Loop over each relation in the xml doc and do a sql insert for each one
                     foreach (XmlElement node in data.DocumentElement.SelectNodes("memberTypes"))
                     {
                         CreateMemberTypes(node);
                     }

                     foreach (XmlElement node in data.DocumentElement.SelectNodes("memberGroups/memberGroup"))
                     {
                         CreateMemberGroup(node);
                     }
                 }
             }
        }

        private static void CreateMemberGroup(XmlElement node)
        {
            MemberGroup.MakeNew(node.Attributes["name"].Value, User.GetUser(0));
        }

        private static void CreateMemberTypes(XmlElement element)
        {
            foreach (XmlElement node in element.SelectNodes("memberType"))
            {
                if (TryGetByAlias(node.Attributes["name"].Value) == null)
                    MemberType.MakeNew(User.GetUser(0), node.Attributes["name"].Value);
                MemberType mt = MemberType.GetByAlias(node.Attributes["name"].Value);
                mt.Text = node.Attributes["name"].Value;
                AddTabs(mt, node);
                AddProperties(mt, node);
                mt.Save();
                AddAppSettingToWebConfig("int.sfMemberType", mt.Id.ToString());

                //return mt;
            }
        }

        private static MemberType TryGetByAlias(string Alias)
        {
            try
            {
                return MemberType.GetByAlias(Alias);

            }
            catch (Exception)
            {
                return null;
            }
        }


        public static void AddProperties(MemberType mt, XmlElement node)
        {
            foreach (XmlNode e in node.SelectNodes("property"))
            {
                string Name = e.Attributes["name"].Value;
                string Alias = e.Attributes["alias"].Value;
                string Type = e.Attributes["type"].Value;
                string Definition = e.Attributes["definition"].Value;
                string Tab = e.Attributes["tab"].Value;
                string Mandatory = e.Attributes["mandatory"].Value;
                string Validation = e.Attributes["validation"].Value;
                string Description = e.Attributes["description"].Value;

                umbraco.cms.businesslogic.datatype.DataTypeDefinition dt;
                try
                {
                    dt =
                        umbraco.cms.businesslogic.datatype.DataTypeDefinition.GetDataTypeDefinition(
                            new Guid(Definition));
                    if (dt == null)
                    {
                        System.Diagnostics.Debug.Fail("can't find datatype with definition " + Definition);
                        continue;
                    }
                }
                catch (Exception)
                {
                    System.Diagnostics.Debug.Fail("can't find datatype with definition " + Definition);
                    continue;
                }
                if (mt.getPropertyType(Alias) == null) mt.AddPropertyType(dt, Alias, Name);
                umbraco.cms.businesslogic.propertytype.PropertyType prop = mt.getPropertyType(Alias);
                prop.Mandatory = bool.Parse(Mandatory);
                prop.TabId = getTabByCaption(mt, Tab).Id;
                prop.ValidationRegExp = Validation;
                prop.Description = Description;
                prop.Save();
            }
        }

        /// <summary>
        /// Adds the tabs.
        /// </summary>
        /// <param name="mt">The mt.</param>
        /// <param name="xml">The XML.</param>
        public static void AddTabs(MemberType mt, XmlNode xml)
        {
            umbraco.cms.businesslogic.ContentType.TabI[] tabs = mt.getVirtualTabs;
            foreach (XmlNode e in xml.SelectNodes("tab"))
            {
                string caption = e.Attributes["name"].Value;
                if (getTabByCaption(mt, caption) != null) continue;
                mt.AddVirtualTab(caption);
            };
        }


        /// <summary>
        /// Gets the tab by caption.
        /// </summary>
        /// <param name="mt">The mt.</param>
        /// <param name="Caption">The caption.</param>
        /// <returns></returns>
        private static umbraco.cms.businesslogic.ContentType.TabI getTabByCaption(MemberType mt, string Caption)
        {
            //If linq can be used 
            //return tabs.SingleOrDefault(tn => tn.Caption == caption)
            foreach (umbraco.cms.businesslogic.ContentType.TabI t in mt.getVirtualTabs)
            {
                if (t.Caption == Caption) return t;
            }
            return null;
        }


    }
}
